package org.lizar.config.group;

import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.lizar.config.Config;
import org.lizar.config.ConfigFile;
import org.lizar.config.Translator;
import org.lizar.config.keys.Keys;
import org.lizar.exception.ConfigKeyLoopTooDeep;
import org.lizar.exception.GroupCodeIsNotExists;
import org.lizar.exception.InvalidFullGroupCode;
import org.lizar.json.JList;
import org.lizar.json.JObject;
import org.lizar.json.JSON;
import org.lizar.util.FileTool;
import org.lizar.util.StringHelper;


/**
 * <pre>
 * Member of config architecture.
 * 
 * Like key config usually group config is defined in proper plugins' init_property method.
 * Then it can be used in everywhere.
 * 
 * All config goes in "/WEB-INF/lizar/keys.json".
 * 
 * support:
 * 		${group.xxx}
 *  	${cfg:xxx}
 *  	${key:xxx}
 *  
 *  caution:
 *  	${group.xxx} group can't contain '.'
 *  
 *  rules 
 *  	${group.xxx} group can't contain '.', and default group is plugin.getClass().getSimpleName() in which the entry is defined.
 * 		@see Config
 * 
 * @see Explain
 *  </pre>
 * 
 * */
public class Group {

	private static Map<String,Explain> code_map=new HashMap<String,Explain>();
	
	private static Map<String,List<Explain>> group_map=new HashMap<String,List<Explain>>();
	
	private static final String FILE_PATH="/WEB-INF/lizar/group.json";
	
	private static Translator  translator;
	
	public static ConfigFile cnf;
	
	private static int size;
	
	private static Group group;
	
	private Group(){
		translator=new GroupTranslator();
	}
	public static Group instance(){
		if(group==null)group=new Group();
		return group;
	}
	
	public static Map<String,Explain> getAll(){
		return code_map;
	}
	
	public  void persistence() throws IOException{
		if(cnf==null){
			cnf=new ConfigFile();
			cnf.file=new File(Config.path_filter(FILE_PATH));
		}
		if(cnf.file.exists()){
			JSON entity=FileTool.read_json(cnf.file);
			if(entity!=null){
				Explain exp;
				JList list;
				JSON st;
				for(String key:entity.keySet()){
					list=entity._list(key);
					for(Object o:list){
						st=(JSON)o;
						exp=new Explain();
						exp.setGroup(key);
						exp.setCode(st.getStr("code"));
						exp.setValue(st.get("value"));
						exp.setDesc(st.getStr("desc"));
						set_to_list(exp);
					}
				}
				translator.translate();
			}
		}
		size=code_map.size();
		FileTool.write_to_file(to_group_entity(), cnf.file,"utf-8");
		cnf.last_modify=cnf.file.lastModified();
	}
	
	private static void set_to_list(Explain exp){
		Explain e=group.code_map.get(exp.getFullCode());
		List<Explain> list=group.group_map.get(exp.getGroup());
		if(list==null)list=new LinkedList<Explain>();
		if(e==null){
			list.add(exp);
		}else{
			for(int i=0;i<list.size();i++){
				if(list.get(i).getFullCode().equals(exp.getFullCode())){
					list.remove(i);
					break;
				}
			}
			list.add(exp);
		}
		group.code_map.put(exp.getFullCode(), exp);
		group.group_map.put(exp.getGroup(),list);
	}
	
	
	private static JSON to_group_entity(){
		JList list;
		JSON data=new JObject();
		JSON exp;
		for(String k:group_map.keySet()){
			List<Explain> exp_list=group_map.get(k);
			list=new JList();
			for(Explain e:exp_list){
				exp=new JObject();
				exp.put("code",e.getCode());
				exp.put("value",e.getValue());
				exp.put("desc", e.getDesc());
				list.add(exp);
			}
			data.put(k+"", list);
		}
		return data;
	}
	
	public static List<Explain> retreiveFromValue(String _group,Object obj){
		List<Explain> list=group.group_map.get(_group);
		if(list==null){
			throw new GroupCodeIsNotExists(_group);
		}
		Iterator<Explain> itr=list.iterator();
		List<Explain> new_list=null;
		Explain exp=null;
		while(itr.hasNext()){
			exp=itr.next();
			if(StringHelper.equals(exp.getValue().toString(),obj.toString())){
				if(new_list==null){
					new_list=new LinkedList();
				}
				new_list.add(exp);
			}
		}
		return new_list;
	}
	
	public static Explain retreiveFirstFromValue(String _group,Object obj){
		List<Explain> list=retreiveFromValue(_group,obj);
		if(list==null||list.isEmpty())return null;
		return list.get(0);
	}
	
	
	public static void setUp(String _group,String code,Object value){
		Explain exp=new Explain();
		exp.setGroup(_group);
		exp.setCode(code);
		exp.setValue(value);
		set_to_list(exp);
	}
	
	public static void setUp(String _group,String code,Object value,String desc){
		Explain exp=new Explain();
		exp.setGroup(_group);
		exp.setCode(code);
		exp.setValue(value);
		exp.setDesc(desc);
		set_to_list(exp);
	}
	
	public static void persist_to_file(Explain exp) throws IOException{
		set_to_list(exp);
		FileTool.write_to_file(to_group_entity(), cnf.file,"utf-8");
		group.persistence();
	}
	
	public static void persist_to_file(List<Explain> explist) throws IOException{
		for(Explain exp:explist){
			set_to_list(exp);
		}
		FileTool.write_to_file(to_group_entity(), cnf.file,"utf-8");
		group.persistence();
	}

	public void check() throws IOException{
		if(!cnf.file.exists()){
			FileTool.write_to_file(to_group_entity(), cnf.file,"utf-8");
			cnf.last_modify=cnf.file.lastModified();
			return;
		}
		if(cnf.file.lastModified()!=cnf.last_modify){
			JSON entity=FileTool.read_json(cnf.file);
			if(entity!=null){
				Explain exp;
				JList list;
				JSON st;
				for(String key:entity.keySet()){
					list=entity._list(key);
					for(Object o:list){
						st=(JSON)o;
						exp=new Explain();
						exp.setGroup(key);
						exp.setCode(st.getStr("code"));
						exp.setValue(st.get("value"));
						exp.setDesc(st.getStr("desc"));
						set_to_list(exp);
					}
				}
				translator.translate();
			}
			FileTool.write_to_file(to_group_entity(), cnf.file,"utf-8");
			cnf.last_modify=cnf.file.lastModified();
		}else if(size!=code_map.size()){
			translator.translate();
			FileTool.write_to_file(to_group_entity(), cnf.file,"utf-8");
			cnf.last_modify=cnf.file.lastModified();
		}
	}
	
	public static List<Explain> getGroupOfCodes(String _group){
		return group.group_map.get(_group);
	}

	public static Explain get(String full_code){
		return group.code_map.get(full_code);
	}

	
	public static Object get(String group,String code){
		Explain exp=get(group+"."+code);
		if(exp==null)throw new GroupCodeIsNotExists(group,code);
		return exp.getValue();
	}
	
	
	public static int getInt(String group,String code){
		Explain exp=get(group+"."+code);
		if(exp==null)throw new GroupCodeIsNotExists(group,code);
		try{
			return Integer.parseInt(exp.getValue().toString());
		}catch(Exception e){
			e.printStackTrace();
			return 0;
		}
	}
	
	public static int getInt(String fullcode){
		Explain exp=get(fullcode);
		if(exp==null)throw new GroupCodeIsNotExists(fullcode);
		try{
			return Integer.parseInt(exp.getValue().toString());
		}catch(Exception e){
			e.printStackTrace();
			return 0;
		}
	}
	
	public static boolean getBool(String fullcode){
		Explain exp=get(fullcode);
		if(exp==null)throw new GroupCodeIsNotExists(fullcode);
		try{
			return Boolean.parseBoolean(exp.getValue().toString());
		}catch(Exception e){
			e.printStackTrace();
			return false;
		}
	}
	
	public static boolean getBool(String fullcode,boolean default_value){
		Explain exp=get(fullcode);
		if(exp==null)throw new GroupCodeIsNotExists(fullcode);
		try{
			return Boolean.parseBoolean(exp.getValue().toString());
		}catch(Exception e){
			e.printStackTrace();
			return default_value;
		}
	}
	
	public static long getLong(String fullcode){
		Explain exp=get(fullcode);
		if(exp==null)throw new GroupCodeIsNotExists(fullcode);
		if(exp.getValue() instanceof Integer) return (Integer)exp.getValue();
		try{
			return Long.parseLong(exp.getValue().toString());
		}catch(Exception e){
			e.printStackTrace();
			return 0;
		}
	}
	
	public static long getLong(String group,String code){
		Explain exp=get(group+"."+code);
		if(exp==null)throw new GroupCodeIsNotExists(group,code);
		if(exp.getValue() instanceof Integer) return (Integer)exp.getValue();
		try{
			return Long.parseLong(exp.getValue().toString());
		}catch(Exception e){
			e.printStackTrace();
			return 0;
		}
	}
	
	
	public static double getDouble(String group,String code){
		Explain exp=get(group+"."+code);
		if(exp==null)throw new GroupCodeIsNotExists(group,code);
		try{
			return Double.parseDouble(exp.getValue().toString());
		}catch(Exception e){
			e.printStackTrace();
			return 0;
		}
	}
	
	public static double getDouble(String fullcode){
		Explain exp=get(fullcode);
		if(exp==null)throw new GroupCodeIsNotExists(fullcode);
		try{
			return Double.parseDouble(exp.getValue().toString());
		}catch(Exception e){
			e.printStackTrace();
			return 0;
		}
	}
	
	public static float getFloat(String fullcode){
		Explain exp=get(fullcode);
		if(exp==null)throw new GroupCodeIsNotExists(fullcode);
		try{
			return Float.parseFloat(exp.getValue().toString());
		}catch(Exception e){
			e.printStackTrace();
			return 0;
		}
		
	}
	
	
	public static boolean _bool(String fullcode){
		Explain exp=get(fullcode);
		if(exp==null)throw new GroupCodeIsNotExists(fullcode);
		try{
			return Boolean.parseBoolean(exp.getValue().toString());
		}catch(Exception e){
			e.printStackTrace();
			return false;
		}
	}
	
	public static String getStr(String group,String code){
		Explain exp=get(group+"."+code);
		if(exp==null)throw new GroupCodeIsNotExists(group,code);
		return (String)exp.getValue();
	}
	
	public static String getStr(String fullcode){
		Explain exp=get(fullcode);
		if(exp==null)throw new GroupCodeIsNotExists(fullcode);
		if(exp.getValue()!=null)return exp.getValue().toString();
		return "";
	}
	
	public static String desc(String fullcode){
		Explain exp=get(fullcode);
		if(exp==null)throw new GroupCodeIsNotExists(fullcode);
		return (String)exp.getDesc();
	}
	
	public static String desc(String group,String code){
		Explain exp=get(group+"."+code);
		if(exp==null)throw new GroupCodeIsNotExists(group,code);
		return (String)exp.getDesc();
	}
	
	
	protected class GroupTranslator extends Translator{
		
		public void translate(){
			Explain k;
			for(Object obj:code_map.values()){
				k=(Explain)obj;
				try{
				k.setValue(express(k.getValue()));
				}catch(Exception e){
					k.setValue(k.getValue());
				}
			}
		}
		
		/**
		 * If value is an instance of string, translate EL value to string value.<br/>
		 * Example: key background's value is  ${color} and color is red and then return red.
		 * @param value may contain EL expression ${}
		 */
		protected Object express(Object value){
			if(value==null)return null;
			if(value instanceof String){
				int i=0;
				Object o;
				String k=value.toString();
				while(true){
					if(i>=20){
						throw new ConfigKeyLoopTooDeep(Config.path_filter(FILE_PATH),value.toString());
					}
					i++;
					if(!Translator.check(k))return k;
					if(k.indexOf(".")==-1)throw new InvalidFullGroupCode(k);
					int start=k.indexOf("${");
					int end=k.indexOf("}");
					if(start==0&&end==k.length()-1){
						o=explain(k.substring(2,k.length()-1));
						if(o instanceof String){
							k=o.toString();
							continue;
						}
						return o;
					}
					Object result=explain(k.substring(start+2, end));
					if(result==null) result=" ";
					if(start==0){
						k= result.toString()+k.substring(end+1,k.length());
						continue;
					}
					if(end==k.length()-1){
						k=k.substring(0,start)+result;
						continue;
					}
					k=k.substring(0,start)+result+k.substring(end+1,k.length());
				}
			}else return value;
		}
		
		private Object explain(String key){
			if(key.startsWith("cfg:")){
				if(key.length()==4)return "";
				return Config.xpath(key.substring(4, key.length()));
			}else if(key.startsWith("key:")){
				if(key.length()==4)return "";
				return Keys.get(key.substring(4, key.length())).value.toString();
			}
			Explain e=group.code_map.get(key);
			if(e==null)throw new GroupCodeIsNotExists(key);
			return e.getValue();
		}
		
	}
	
}
